<?php

class PhpDocParser
{
	public function parse ($file)
	{
		$res = array();

		$s = file_get_contents($file);
		$b = 0;
		if ($t = $this->parse_doc_file($s,$b)) {
			$res['file'] = $t;
		}
		while ($s 
				&& false !== ($b1 = strpos($s, '/**')) 
				&& false !== ($e1 = strpos(substr($s,$b1), '*/'))) {
			$doc = substr($s, $b1+3, $e1 - 3);
			$s = substr($s, $b1 + $e1 + 2);
			$b += $b1 + $e1 + 2;
			if ($t = $this->parse_doc_var($s,$b, $doc)) {
				$res['docs'][] = $t;
			}
		}
		return $res;
	} // end of function 

	public function parse_doc_file (&$s,&$b)
	{
		$res = array();
		if (preg_match('#^\\s*<\?php\s+/\*\*(.*?)\*/#si', $s, $ms, PREG_OFFSET_CAPTURE)) {
			$s = substr($s, strlen($ms[0][0]));
			$res = $this->parse_doc_string($ms[1][0]);
			$b = strlen($ms[0][0]);
		}
		return $res;
	} // end of method parse_doc_file
	public function parse_doc_var (&$s,&$b, $doc)
	{
		$res = array();
		if (preg_match('#^\s*(?:(var|public|protected|private)\s*)?(\$[^;]+?)\s*=\s*(.*?);#s', $s, $ms, PREG_OFFSET_CAPTURE)
				&& ($td = $this->parse_doc_string($doc))) {
			$td['tags'][] = array('_type' => 'name', 'value' => $ms[2][0]);
			$td['tags'][] = array('_type' => 'value', 'value' => $ms[3][0], '_b'=>$b+$ms[3][1], '_l'=>strlen($ms[3][0]));
			$res = $td;
		}
		return $res;
	} // end of method parse_doc_file

	public function parse_tag_title (&$s)
	{
		$res = array();
		$q = '';
		do {
			if (preg_match('#^\s*\n\s*\n#', $s)) break;
			if (preg_match('#^\s*[^@].*?([\n.]?)#', $s, $ms)) {
				$s = substr($s, strlen($ms[0]));
				$q .= $ms[0];
				if ($ms[1] == '.') break;
			} else {
				break;
			}
		} while ($s);
		$q = trim($q);
		if ($q) {
			$res['_type'] = 'title';
			$res['value'] = implode('', explode("\n", $q));
		}
		return $res;
	} // end of method parse_title
	public function parse_tag_doc (&$s)
	{
		$res = array();
		$q = '';
		do {
			if (preg_match('#^\s*[^@].*?[\n]?#', $s, $ms)) {
				$s = substr($s, strlen($ms[0]));
				$q .= $ms[0];
			} else {
				break;
			}
		} while ($s);
		$q = trim($q);
		if ($q) {
			$res['_type'] = 'doc';
			$res['value'] = implode('', explode("\n", $q));
		}
		return $res;
	} // end of method parse_title
	public function parse_tag_var (&$s)
	{
		$res = array();
		if ($res = $this->parse_tag($s)) {;
			if (preg_match('#^\s*([[:alnum:]]+)\s*#', $res['value'], $ms)) {
				$res['type'] = $ms[1] ? $ms[1] : 'mixed';
				$res['doc']  = substr($res['value'], strlen($ms[0]));
			} else {
				$res['type'] = 'mixed';
				$res['doc']  = $res['value'];
			}
		}
		return $res;
	} // end of function parse_tag
	public function parse_tag_access (&$s)
	{
		$res = array();
		if ($res = $this->parse_tag($s)) {
			if (preg_match('#^\s*([[:alnum:]]+)\s*#', $res['value'], $ms)) {
				$res['access'] = $ms[1];
				$res['value']  = substr($res['value'], strlen($ms[0]));
			} else {
			}
		}
		return $res;
	} // end of function parse_tag
	public function parse_tag (&$s)
	{
		$res = array();
		if (preg_match('#^\s*@([[:alnum:]]+)\s*#', $s, $ms)) {
			list($all, $tag) = $ms;
			$s = substr($s, strlen($all));
			$q = '';
			do {
				if (preg_match('#^\s*[^@].*?[\n]?#', $s, $ms)) {
					$s = substr($s, strlen($ms[0]));
					$q .= $ms[0];
				} else {
					break;
				}
			} while ($s);
			$res['_type'] = $tag;
			$res['value'] = trim($q);
		}
		return $res;
	} // end of function parse_tag
	public function parse_tags (&$s)
	{
		$t = $s;
		$res = array();
		if (preg_match('#^\s*@([[:alnum:]]+)#', $t, $ms)) {
			list($all, $tag) = $ms;
			$mtag = 'parse_tag';
			$mtag_s = $mtag.'_'.$tag;
			$m = method_exists($this, $mtag_s) ? $mtag_s : $mtag;
			$res = $this->$m($s);
		}
		return $res;
	} // end of method parse_title
	public function parse_doc_string ($s)
	{
		$s = preg_replace('#^\s*\*#m','',$s);
		$res = array();
		if ($node = $this->parse_tag_title($s)) {
			$res['tags'][] = $node;
		}
		if ($node = $this->parse_tag_doc($s)) {
			$res['tags'][] = $node;
		}
		while ($node = $this->parse_tags($s)) {
			$res['tags'][] = $node;
		}
		return $res;
	}
}

class PhpDocUtils
{
	static public function findTags ($name, $ts)
	{
		$tags = array();
		foreach ($ts['tags'] as &$t) {
			$ttype = $t['_type'];
			if (!isset($tags[$ttype])) $tags[$ttype] = 0;
			$tags[$ttype]++;
		}
		return $tags;
	} // end of method findFistTagByName
	static public function findFirstTagByName ($name, $ts)
	{
		foreach ($ts['tags'] as &$t) {
			if ($t['_type'] === $name) return $t;
		}
		return NULL;
	} // end of method findFirstTagByName
	static public function findTagsByName ($name, $ts)
	{
		$res = array();
		foreach ($ts['tags'] as &$t) {
			if ($t['_type'] === $name) $res[] = $t;
		}
		return $res;
	} // end of method findTagsByName
	static public function extractPhpValue ($v)
	{
		$v = trim($v);
		if (preg_match('#^([\'"])(.*)\1$#', $v, $ms)) {
			return $ms[2];
		}
		return $v;
	} // end of method extractPhpValue
	static public function buildPhpValue ($what)
	{
		$res = '"'.
				str_replace( "\\", "\\\\",
				str_replace( "\"", "\\\"",
				str_replace( "\$", "\\\$",
				str_replace( "\n", "\\n",
				str_replace( "\r", "",
				$what ))))).'"';
		return $res;
	} // end of method extractPhpValue
}
